Popular Searches
Popular Course Categories
Popular Courses

Implementing Light and Dark Themes

Implementing Light and Dark Themes

UI Libraries, Themes, and Advanced Interface Design

Implementing Light and Dark Themes in React Native

Course: React Native Training

Topic: Implementing Light and Dark Themes

Course Link: React Native Training at JustAcademy

Demo Registration: Register for a Course Demo

JustAcademy Website: Visit JustAcademy


1. Introduction to Light and Dark Themes

Themes define the visual appearance of a mobile application, including background colors, text colors, buttons, cards, icons, borders, and navigation elements. A light theme typically uses light backgrounds with darker text, while a dark theme uses darker backgrounds with lighter text.

React Native applications can support both themes and allow users to switch between them. The application can follow the device's system appearance automatically or provide an in-app setting that lets users select their preferred theme.

For example, a learning application might display a white background with dark text during light mode and a dark gray background with light text during dark mode. The layout and functionality remain the same while the colors change.

1.1 What Is a Light Theme?

A light theme uses bright backgrounds, dark text, and carefully selected accent colors. It is commonly used in applications that prioritize a clean, familiar appearance and readability in well-lit environments.

  • White or light gray screen backgrounds.
  • Dark gray or black primary text.
  • Light-colored cards and input fields.
  • Dark borders or subtle shadows for separation.
  • Accent colors for buttons, links, and important actions.

1.2 What Is a Dark Theme?

A dark theme uses dark backgrounds and lighter foreground elements. It can reduce the brightness of the interface in dim environments and may be preferred by users who find dark interfaces more comfortable.

  • Dark gray, charcoal, or black backgrounds.
  • Light-colored primary text.
  • Dark surfaces for cards and dialogs.
  • Muted secondary text with sufficient contrast.
  • Accent colors adjusted to remain visible against dark surfaces.

1.3 What Is Automatic Theme Switching?

Automatic theme switching means the application detects the operating system's preferred color scheme and updates its interface accordingly. React Native provides the useColorScheme hook for observing changes to the device's preferred color scheme.

Reference: React Native useColorScheme Documentation.

2. Learning Objectives

After completing this topic, learners should be able to:

  • Understand light themes, dark themes, and system-based appearance.
  • Use the React Native useColorScheme hook.
  • Create separate color palettes for light and dark modes.
  • Apply themes to backgrounds, text, cards, buttons, and input fields.
  • Implement a manual theme switcher.
  • Support system, light, and dark theme preferences.
  • Organize theme configuration into reusable files.
  • Persist a user's theme preference between application sessions.
  • Implement theme support in React Native Paper.
  • Test themes for accessibility, consistency, and platform compatibility.

3. Why Are Light and Dark Themes Important?

Theme support improves personalization and allows users to select an interface that suits their environment and preferences.

3.1 Benefits

  • User preference: Users can choose an appearance that suits their needs.
  • Comfort: Dark interfaces may be preferable in dim environments, although comfort varies by user.
  • Consistency: A centralized theme keeps colors consistent throughout the application.
  • Maintainability: Updating a theme is easier when colors are defined in one place.
  • Brand identity: Both themes can use a consistent set of brand colors.
  • Accessibility: Thoughtful color and contrast choices can improve readability.
  • Platform integration: Following the system appearance can make the application feel more integrated with the device.

3.2 Common Applications

  • Social media applications.
  • News and reading applications.
  • Learning management systems.
  • Banking and finance applications.
  • Shopping applications.
  • Productivity tools and dashboards.
  • Messaging applications.

4. Understanding React Native Appearance APIs

React Native provides the Appearance API and the useColorScheme hook to access the preferred color scheme. For most functional components, useColorScheme is the recommended starting point because it subscribes to changes.

4.1 The useColorScheme Hook

The hook returns the preferred appearance, which can be "light", "dark", or null when the preference is unavailable.

import React from 'react';
import { Text, View, useColorScheme } from 'react-native';

export default function App() {
  const colorScheme = useColorScheme();

  return (
    <View>
      <Text>
        Current scheme: {colorScheme ?? 'unknown'}
      </Text>
    </View>
  );
}

4.2 Explanation

  • useColorScheme is imported from react-native.
  • The hook reads the current preferred color scheme.
  • The component re-renders when the observed system preference changes.
  • The returned value can be used to choose theme colors and component styles.

4.3 The Appearance API

The Appearance API provides methods for reading and, where supported, overriding the application's color scheme.

import { Appearance } from 'react-native';

const currentScheme = Appearance.getColorScheme();

console.log(currentScheme);

// Restore following the system appearance:
Appearance.setColorScheme('auto');

Use the current React Native documentation for the exact supported values in your installed version. On modern versions, Appearance.setColorScheme('auto') restores following the system preference. This API changes the app's appearance behavior; it does not change the system setting for other applications.

Reference: React Native Appearance Documentation.

5. Creating a Basic Light and Dark Theme

The simplest implementation uses conditional colors based on the current color scheme. This approach is suitable for learning and small applications.

5.1 Basic Example

import React from 'react';
import {
  View,
  Text,
  StyleSheet,
  useColorScheme
} from 'react-native';

export default function App() {
  const colorScheme = useColorScheme();
  const isDark = colorScheme === 'dark';

  return (
    <View
      style={[
        styles.container,
        {
          backgroundColor: isDark ? '#121212' : '#FFFFFF'
        }
      ]}
    >
      <Text
        style={[
          styles.title,
          {
            color: isDark ? '#FFFFFF' : '#222222'
          }
        ]}
      >
        Welcome to My App
      </Text>

      <Text
        style={{
          color: isDark ? '#CCCCCC' : '#555555'
        }}
      >
        Light and dark themes are supported.
      </Text>
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    padding: 24
  },
  title: {
    fontSize: 24,
    fontWeight: 'bold',
    marginBottom: 12
  }
});

5.2 How It Works

  1. The application reads the system color scheme with useColorScheme.
  2. The variable isDark becomes true when the scheme is dark.
  3. The background uses a dark color in dark mode and white in light mode.
  4. The title and description also change color.
  5. When the system appearance changes, the component updates its appearance.

5.3 Limitations of the Basic Approach

Conditional styling is simple, but repeating color conditions in every component can make a large application difficult to maintain. A better solution is to define a centralized theme object and reuse it across screens.

6. Creating Centralized Theme Configuration

A centralized theme configuration stores the colors and other visual values used throughout an application. Instead of defining colors repeatedly, components read them from the active theme.

6.1 Suggested Folder Structure

MyApp/
├── App.tsx
└── src/
    ├── theme/
    │   ├── colors.ts
    │   ├── lightTheme.ts
    │   └── darkTheme.ts
    ├── components/
    │   ├── ThemeButton.tsx
    │   └── ProfileCard.tsx
    └── screens/
        └── HomeScreen.tsx

6.2 Defining the Theme Colors

Create a file named src/theme/colors.ts.

export const lightTheme = {
  mode: 'light' as const,
  colors: {
    background: '#FFFFFF',
    surface: '#F5F5F5',
    primary: '#2563EB',
    text: '#1F2937',
    secondaryText: '#6B7280',
    border: '#D1D5DB',
    card: '#FFFFFF',
    success: '#15803D',
    danger: '#B91C1C'
  }
};

export const darkTheme = {
  mode: 'dark' as const,
  colors: {
    background: '#121212',
    surface: '#1E1E1E',
    primary: '#8AB4F8',
    text: '#F9FAFB',
    secondaryText: '#C0C4CC',
    border: '#3F3F46',
    card: '#242424',
    success: '#86EFAC',
    danger: '#FCA5A5'
  }
};

export type AppTheme =
  | typeof lightTheme
  | typeof darkTheme;

6.3 Understanding the Theme Properties

Property Purpose
background Main screen background.
surface Background for secondary sections, panels, or controls.
primary Brand color for important actions and highlights.
text Primary text color.
secondaryText Supporting text, descriptions, and captions.
border Divider and outline color.
card Background color for cards and grouped content.
success Positive status messages.
danger Error or destructive-action indicators.

Keeping these properties consistent allows developers to switch themes without rewriting each component's styles.

7. Applying the Theme to Reusable Components

Once theme objects exist, pass the active theme to reusable components or expose it through React Context. For a small example, passing a theme as a prop is straightforward.

7.1 Creating a Themed Card

import React from 'react';
import {
  View,
  Text,
  StyleSheet
} from 'react-native';

export default function ProfileCard({ theme }) {
  return (
    <View
      style={[
        styles.card,
        {
          backgroundColor: theme.colors.card,
          borderColor: theme.colors.border
        }
      ]}
    >
      <Text
        style={[
          styles.title,
          { color: theme.colors.text }
        ]}
      >
        User Profile
      </Text>

      <Text style={{ color: theme.colors.secondaryText }}>
        Manage your personal information.
      </Text>
    </View>
  );
}

const styles = StyleSheet.create({
  card: {
    padding: 16,
    borderWidth: 1,
    borderRadius: 12
  },
  title: {
    fontSize: 18,
    fontWeight: '700',
    marginBottom: 8
  }
});

7.2 Using the Component

import React from 'react';
import { View, useColorScheme } from 'react-native';
import { lightTheme, darkTheme } from './src/theme/colors';
import ProfileCard from './src/components/ProfileCard';

export default function App() {
  const colorScheme = useColorScheme();
  const theme = colorScheme === 'dark' ? darkTheme : lightTheme;

  return (
    <View style={{ flex: 1, backgroundColor: theme.colors.background }}>
      <ProfileCard theme={theme} />
    </View>
  );
}

This approach works well for small projects. In a larger application, React Context can distribute the theme without passing the same prop through many layers of components.

8. Implementing a Manual Theme Switcher

Some applications let users select light or dark mode through an in-app setting. This requires theme state because the user's preference may differ from the system setting.

8.1 Understanding Theme Preferences

  • System: Follow the device's current appearance.
  • Light: Always use the light theme within the app.
  • Dark: Always use the dark theme within the app.

It is useful to store the user's preference as "system", "light", or "dark". The active theme is then calculated from that preference and the system scheme.

8.2 Complete Example with a Theme Toggle

import React, { useState } from 'react';
import {
  View,
  Text,
  Switch,
  StyleSheet,
  useColorScheme
} from 'react-native';

export default function App() {
  const systemScheme = useColorScheme();
  const [darkMode, setDarkMode] = useState(
    systemScheme === 'dark'
  );

  const theme = darkMode
    ? {
        background: '#121212',
        text: '#FFFFFF',
        surface: '#242424',
        primary: '#8AB4F8'
      }
    : {
        background: '#FFFFFF',
        text: '#222222',
        surface: '#F2F2F2',
        primary: '#2563EB'
      };

  return (
    <View
      style={[
        styles.container,
        { backgroundColor: theme.background }
      ]}
    >
      <Text style={[styles.title, { color: theme.text }]}>
        Theme Settings
      </Text>

      <View
        style={[
          styles.row,
          { backgroundColor: theme.surface }
        ]}
      >
        <Text style={{ color: theme.text }}>
          Enable Dark Mode
        </Text>

        <Switch
          value={darkMode}
          onValueChange={setDarkMode}
          accessibilityLabel="Enable dark mode"
        />
      </View>

      <Text style={{ color: theme.primary, marginTop: 20 }}>
        {darkMode ? 'Dark mode is active' : 'Light mode is active'}
      </Text>
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    padding: 24,
    justifyContent: 'center'
  },
  title: {
    fontSize: 24,
    fontWeight: 'bold',
    marginBottom: 20
  },
  row: {
    padding: 16,
    borderRadius: 12,
    flexDirection: 'row',
    justifyContent: 'space-between',
    alignItems: 'center'
  }
});

8.3 Important Detail About This Example

The switch controls local state, so it can change the theme manually. However, after the initial state is created, the example does not automatically synchronize the switch with later system changes. A production application should explicitly implement the three preference modes and determine the active theme from the selected preference and current system scheme.

9. Supporting System, Light, and Dark Preferences

A robust implementation distinguishes the user's selected preference from the active theme. For example, if the user selects system, the application follows the device. If the user selects dark, it stays dark even if the device is set to light mode.

9.1 Theme Selection Logic

type ThemePreference = 'system' | 'light' | 'dark';

function getActiveScheme(
  preference: ThemePreference,
  systemScheme: 'light' | 'dark' | null
): 'light' | 'dark' {
  if (preference === 'light') {
    return 'light';
  }

  if (preference === 'dark') {
    return 'dark';
  }

  return systemScheme === 'dark' ? 'dark' : 'light';
}

9.2 Using the Logic in a Component

import React, { useState } from 'react';
import {
  View,
  Text,
  Pressable,
  useColorScheme
} from 'react-native';
import { lightTheme, darkTheme } from './src/theme/colors';

type ThemePreference = 'system' | 'light' | 'dark';

export default function App() {
  const systemScheme = useColorScheme();

  const [preference, setPreference] =
    useState<ThemePreference>('system');

  const activeScheme =
    preference === 'system'
      ? (systemScheme === 'dark' ? 'dark' : 'light')
      : preference;

  const theme =
    activeScheme === 'dark' ? darkTheme : lightTheme;

  return (
    <View
      style={{
        flex: 1,
        padding: 24,
        backgroundColor: theme.colors.background
      }}
    >
      <Text
        style={{
          fontSize: 24,
          color: theme.colors.text,
          marginBottom: 20
        }}
      >
        Choose Appearance
      </Text>

      {(['system', 'light', 'dark'] as const).map(option => (
        <Pressable
          key={option}
          accessibilityRole="button"
          accessibilityState={{
            selected: preference === option
          }}
          onPress={() => setPreference(option)}
          style={{
            padding: 14,
            marginBottom: 10,
            backgroundColor: theme.colors.surface,
            borderColor: theme.colors.border,
            borderWidth: preference === option ? 2 : 1,
            borderRadius: 8
          }}
        >
          <Text style={{ color: theme.colors.text }}>
            {option === 'system'
              ? 'Follow System'
              : option === 'light'
                ? 'Light Mode'
                : 'Dark Mode'}
          </Text>
        </Pressable>
      ))}
    </View>
  );
}

9.3 How It Works

  1. The component reads the system scheme through useColorScheme.
  2. The preference state stores the user's selected mode.
  3. If the preference is light or dark, that choice determines the active theme.
  4. If the preference is system, the active theme follows the system scheme.
  5. The chosen theme supplies the colors used by the screen.

10. Sharing Theme State with React Context

As an application grows, many components need access to the active theme. React Context provides a way to share theme state and update functions without manually passing props through every component.

10.1 Suggested Architecture

src/
├── theme/
│   ├── colors.ts
│   ├── ThemeContext.tsx
│   └── useAppTheme.ts
├── components/
│   ├── ThemedText.tsx
│   └── ThemedCard.tsx
└── screens/
    ├── HomeScreen.tsx
    └── SettingsScreen.tsx

10.2 Creating a Theme Context

import React, {
  createContext,
  useContext,
  useMemo,
  useState
} from 'react';
import { useColorScheme } from 'react-native';
import { lightTheme, darkTheme } from './colors';

type ThemePreference = 'system' | 'light' | 'dark';

type ThemeContextValue = {
  preference: ThemePreference;
  setPreference: (value: ThemePreference) => void;
  theme: typeof lightTheme | typeof darkTheme;
};

const ThemeContext = createContext<ThemeContextValue | null>(null);

export function ThemeProvider({ children }) {
  const systemScheme = useColorScheme();

  const [preference, setPreference] =
    useState<ThemePreference>('system');

  const activeScheme =
    preference === 'system'
      ? systemScheme === 'dark' ? 'dark' : 'light'
      : preference;

  const theme =
    activeScheme === 'dark' ? darkTheme : lightTheme;

  const value = useMemo(
    () => ({ preference, setPreference, theme }),
    [preference, theme]
  );

  return (
    <ThemeContext.Provider value={value}>
      {children}
    </ThemeContext.Provider>
  );
}

export function useAppTheme() {
  const context = useContext(ThemeContext);

  if (!context) {
    throw new Error(
      'useAppTheme must be used inside ThemeProvider'
    );
  }

  return context;
}

10.3 Wrapping the Application

import React from 'react';
import { ThemeProvider } from './src/theme/ThemeContext';
import HomeScreen from './src/screens/HomeScreen';

export default function App() {
  return (
    <ThemeProvider>
      <HomeScreen />
    </ThemeProvider>
  );
}

10.4 Accessing the Theme in a Screen

import React from 'react';
import { View, Text } from 'react-native';
import { useAppTheme } from '../theme/ThemeContext';

export default function HomeScreen() {
  const { theme } = useAppTheme();

  return (
    <View
      style={{
        flex: 1,
        padding: 24,
        backgroundColor: theme.colors.background
      }}
    >
      <Text style={{ color: theme.colors.text }}>
        Themed Home Screen
      </Text>
    </View>
  );
}

Note: The example demonstrates the main architecture. In a production TypeScript project, define explicit prop types for providers and components, and ensure the theme object types are compatible.

11. Persisting Theme Preferences

When a user manually chooses a theme, they usually expect that choice to remain after closing and reopening the application. This requires storing the selected preference locally.

11.1 What Should Be Stored?

Store the preference rather than only the active color scheme. For example, save system, light, or dark. If the user selects system mode, the application can still respond to later changes in device settings.

11.2 Using AsyncStorage

AsyncStorage is a persistent key-value storage option commonly used in React Native projects. Install it according to the current official documentation and the requirements of your project.

Documentation: React Native AsyncStorage.

11.3 Example: Saving and Loading the Preference

import AsyncStorage from '@react-native-async-storage/async-storage';

const THEME_KEY = '@app_theme_preference';

export async function saveThemePreference(
  preference: 'system' | 'light' | 'dark'
) {
  try {
    await AsyncStorage.setItem(THEME_KEY, preference);
  } catch (error) {
    console.error('Unable to save theme preference', error);
  }
}

export async function loadThemePreference():
  Promise<'system' | 'light' | 'dark'> {
  try {
    const value = await AsyncStorage.getItem(THEME_KEY);

    if (value === 'light' || value === 'dark' || value === 'system') {
      return value;
    }
  } catch (error) {
    console.error('Unable to load theme preference', error);
  }

  return 'system';
}

11.4 Integrating Persistence

When the application starts, load the stored preference and use it to initialize theme state. When the user changes the preference, update the state and save the new value. Consider a loading state if the application must avoid briefly displaying the wrong theme before the stored preference is loaded.

For production applications, handle storage errors and avoid overwriting a saved preference with the default value before the initial read has completed.

12. Implementing Themes with React Native Paper

React Native Paper is a Material Design component library that supports light and dark themes. It provides PaperProvider, theme objects, and the useTheme hook.

Official documentation: React Native Paper Theming Guide.

12.1 Installing React Native Paper

npm install react-native-paper

Check the current installation guide for any additional requirements for your React Native or Expo project.

12.2 Using Built-in Light and Dark Themes

import React from 'react';
import {
  PaperProvider,
  MD3LightTheme,
  MD3DarkTheme,
  Button,
  Card,
  Text
} from 'react-native-paper';
import { useColorScheme, View } from 'react-native';

export default function App() {
  const colorScheme = useColorScheme();

  const theme =
    colorScheme === 'dark'
      ? MD3DarkTheme
      : MD3LightTheme;

  return (
    <PaperProvider theme={theme}>
      <View style={{ padding: 20 }}>
        <Text variant="headlineMedium">
          Theme Example
        </Text>

        <Card style={{ marginVertical: 16 }}>
          <Card.Content>
            <Text variant="titleLarge">
              React Native Paper
            </Text>
            <Text>
              This interface follows the device appearance.
            </Text>
          </Card.Content>
        </Card>

        <Button mode="contained" onPress={() => {}}>
          Continue
        </Button>
      </View>
    </PaperProvider>
  );
}

12.3 Explanation

  • MD3LightTheme provides the Material Design 3 light theme.
  • MD3DarkTheme provides the corresponding dark theme.
  • useColorScheme detects the current system preference.
  • PaperProvider makes the selected theme available to Paper components.
  • Paper components use theme values for their appearance.

12.4 Customizing the Theme

import {
  MD3LightTheme,
  MD3DarkTheme
} from 'react-native-paper';

export const customLightTheme = {
  ...MD3LightTheme,
  colors: {
    ...MD3LightTheme.colors,
    primary: '#1D4ED8',
    secondary: '#0F766E',
    background: '#FFFFFF'
  }
};

export const customDarkTheme = {
  ...MD3DarkTheme,
  colors: {
    ...MD3DarkTheme.colors,
    primary: '#93C5FD',
    secondary: '#5EEAD4',
    background: '#121212'
  }
};

Extend the appropriate default theme and preserve the other required theme properties. Theme structures may vary by library version, so consult the current documentation before adding or overriding values.

13. Theming Common UI Elements

Changing only the screen background is not sufficient. Every visible interface element must be reviewed to ensure the selected theme is applied consistently.

13.1 Buttons

  • Choose a primary button color that contrasts with its label.
  • Check pressed, focused, and disabled states.
  • Ensure destructive buttons remain distinguishable in both themes.

13.2 Cards

  • Use a suitable surface color for cards.
  • Maintain visible separation from the screen background.
  • Adjust borders and shadows for dark surfaces.

13.3 Text Inputs

  • Update the input background and text color.
  • Use visible placeholder and label colors.
  • Keep error messages readable.
  • Ensure focus borders are visible in both themes.

13.4 Navigation Bars and Status Bars

Navigation elements and system bars may require separate configuration depending on the navigation library and platform. Check status bar icon color, navigation bar background, safe areas, and modal appearance on both Android and iOS.

13.5 Images and Icons

Some image assets are suitable for both themes, while others need theme-specific versions. Logos, illustrations, and icons should be checked for visibility against light and dark backgrounds.

14. Designing Accessible Light and Dark Themes

Accessibility is an essential part of theme implementation. A dark theme is not automatically accessible simply because it uses dark colors. Text, icons, borders, and interactive states must remain clear in both modes.

14.1 Contrast

Maintain sufficient contrast between text and its background. For ordinary text, WCAG commonly recommends a contrast ratio of at least 4.5:1, while large text generally requires at least 3:1. These are useful design checks, although platform-specific requirements and context should also be considered.

14.2 Accessibility Best Practices

  • Do not use color as the only indicator of errors or success.
  • Use accessible labels for icon-only controls.
  • Keep text readable when users increase font size.
  • Check focus indicators and disabled states.
  • Test light and dark modes using screen readers.
  • Avoid pure white text on pure black backgrounds if the resulting appearance is uncomfortable for your intended audience.
  • Check color contrast for links, placeholders, dividers, and secondary text.

15. Testing Light and Dark Themes

Theme testing verifies that the entire interface updates correctly when the user changes the system appearance or in-app preference.

15.1 Testing Checklist

  • Open the application with the device set to light mode.
  • Switch the device to dark mode while the application is running.
  • Confirm that screen backgrounds and text update.
  • Check cards, dialogs, menus, and input fields.
  • Test the manual theme preference if the application provides one.
  • Confirm that system mode follows later device setting changes.
  • Close and reopen the application to test saved preferences.
  • Check navigation bars and status bar icons.
  • Inspect images and icons for visibility.
  • Test both Android and iOS where applicable.

15.2 Testing in Expo

For an Expo project, use the supported appearance configuration for the installed SDK. Expo documents the userInterfaceStyle setting and automatic system appearance behavior.

Documentation: Expo Color Themes.

16. Common Problems and Troubleshooting

Problem Possible Cause Solution
Theme does not change with device settings Color scheme is read once or the hook is not used correctly Use useColorScheme in the component that needs the current scheme.
Only the background changes Text, cards, and controls use fixed colors Use centralized theme colors throughout the interface.
Manual switch does not follow system changes Manual state is being used without a system preference mode Separate the stored preference from the calculated active scheme.
Saved preference is lost Preference exists only in component state Persist the user's selection using suitable local storage.
Wrong theme appears briefly at startup Stored preference is loaded asynchronously Wait for the initial preference load or provide an appropriate startup state.
Text is difficult to read Insufficient contrast or unsuitable secondary text colors Adjust the palette and test contrast.
React Native Paper components use unexpected colors Provider or theme configuration is incorrect Verify the selected theme and the PaperProvider setup.
Icons disappear in dark mode Fixed dark icon assets are used on dark backgrounds Use theme-aware icon colors or suitable alternate assets.
Status bar remains difficult to read System bar style is not coordinated with the theme Configure and test status bar appearance for each platform.
Theme differs between platforms Native controls or platform defaults behave differently Test on real target platforms and adjust supported platform-specific settings.

17. Best Practices for Theme Implementation

  1. Centralize colors: Define theme palettes in a dedicated module.
  2. Use semantic names: Prefer background, surface, text, and primary over names tied to a specific color.
  3. Separate preference from appearance: Keep the user's system/light/dark choice separate from the currently active scheme.
  4. Reuse theme values: Avoid hardcoding colors throughout the application.
  5. Support system changes: Use useColorScheme when system preference should be observed.
  6. Persist explicit choices: Store manual preferences if they should survive application restarts.
  7. Test every component: Review dialogs, menus, loading states, forms, and error messages.
  8. Check accessibility: Validate contrast, text scaling, and screen-reader support.
  9. Minimize flicker: Handle asynchronous preference loading carefully.
  10. Document theme tokens: Help the team understand which token to use for each UI purpose.

18. Mini Project: Light and Dark Theme Settings Screen

Project objective: Build a settings screen that lets users choose system, light, or dark mode and applies the selected theme to the entire application.

18.1 Requirements

  • A settings screen with three theme options.
  • System mode that follows the device appearance.
  • A light theme with appropriate background and text colors.
  • A dark theme with readable text and visible controls.
  • Reusable theme configuration.
  • Persistent storage for the user's preference.
  • Consistent theme application across multiple screens.
  • Accessible labels and selected-state indicators.

18.2 Implementation Steps

  1. Create a React Native project.
  2. Define the light and dark theme objects.
  3. Use useColorScheme to observe the system preference.
  4. Create a theme context that stores the selected preference.
  5. Add system, light, and dark options to the settings screen.
  6. Calculate the active theme from the preference and system scheme.
  7. Apply theme values to the background, text, cards, and buttons.
  8. Save the selected preference in local storage.
  9. Load the saved preference when the application starts.
  10. Test system changes, app restarts, and accessibility in both modes.

18.3 Expected Result

The application should display the appropriate theme throughout the interface. Selecting light or dark should override the system preference inside the app, while selecting system should restore automatic behavior.

19. Practice Exercises

  1. Create a basic screen that changes its background and text colors according to the device theme.
  2. Create light and dark theme objects in a separate file.
  3. Build a reusable themed card component.
  4. Implement a switch for manually changing the theme.
  5. Extend the switch implementation to support system, light, and dark preferences.
  6. Use React Context to share theme state across multiple screens.
  7. Save and restore the user's selected theme with AsyncStorage.
  8. Integrate theme switching with React Native Paper.
  9. Check contrast ratios for primary and secondary text in both themes.
  10. Test theme behavior on Android and iOS devices or simulators.

20. Interview Questions and Answers

Q1. What is a theme in React Native?

Answer: A theme is a collection of visual settings, such as colors and other design values, that defines how an application's interface appears.

Q2. What is the difference between light mode and dark mode?

Answer: Light mode generally uses light backgrounds and dark text, whereas dark mode uses dark backgrounds and lighter text.

Q3. What is the useColorScheme hook?

Answer: It is a React Native hook that provides the current preferred color scheme and subscribes to appearance changes.

Q4. What values can useColorScheme return?

Answer: It commonly returns light, dark, or null if the preference is unavailable.

Q5. What is the Appearance API?

Answer: It provides methods for reading the preferred appearance, listening for changes, and controlling the application's appearance override where supported.

Q6. Why should theme colors be centralized?

Answer: Centralized theme values reduce duplication, improve consistency, and make it easier to update the visual design.

Q7. How can a user manually change themes?

Answer: Store a theme preference in state and provide controls that select light or dark mode. Calculate the active theme from that preference.

Q8. How can the application follow the system theme?

Answer: Use useColorScheme and select the light or dark theme based on the returned value. An explicit system preference should use this behavior instead of forcing one mode.

Q9. How can a theme preference persist after restarting the application?

Answer: Store the preference in persistent local storage, load it when the application starts, and update it whenever the user changes the setting.

Q10. What is React Native Paper?

Answer: React Native Paper is a Material Design component library that supports themes through PaperProvider and theme objects.

Q11. Why is accessibility important for dark themes?

Answer: Text and controls must remain readable and distinguishable. Developers should verify contrast, text scaling, focus states, and screen-reader behavior.

Q12. What causes theme flickering on startup?

Answer: The application may render a default theme before an asynchronously stored preference has loaded. A suitable initial loading strategy can reduce this issue.

21. Quick Revision Checklist

  • Understand light mode, dark mode, and system mode.
  • Use useColorScheme to observe system appearance changes.
  • Understand the purpose of the Appearance API.
  • Create centralized light and dark theme objects.
  • Apply semantic theme values to every visible component.
  • Separate the selected preference from the active theme.
  • Use React Context when multiple screens need shared theme state.
  • Persist user-selected preferences when required.
  • Configure theme providers when using UI libraries such as React Native Paper.
  • Test contrast, icons, system bars, forms, dialogs, and navigation.
  • Verify behavior on the supported Android and iOS versions.

22. Official Learning Resources

23. Conclusion

Implementing light and dark themes in React Native makes an application more adaptable and gives users greater control over its appearance. The useColorScheme hook supports system-based theme detection, while centralized theme objects make styling consistent and maintainable.

For larger applications, React Context can distribute the active theme, and local storage can preserve explicit user preferences. UI libraries such as React Native Paper provide additional theme support for their components. A complete implementation should update the entire interface, respect system changes when requested, preserve user choices, and maintain accessibility in both modes.

To continue learning, explore the React Native Training course at JustAcademy or register for a course demo.

whatsapp